﻿using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System;
using System.Diagnostics;
using System.IO;
using System.Xml.XPath;
using System.Xml;
using System.Reflection;

public static class ApplicationExtensions {

    private static string logo;
    private static int counter = 0;
    private static Dictionary<string, XmlDocument> docCache = new Dictionary<string, XmlDocument>();
    private static Dictionary<string, List<string>> switches;
    private static List<string> parameters;
    private static Stack<ConsoleColor> colors = new Stack<ConsoleColor>();
    private static CredentialCache credCache;

    public static XmlDocument xPath(this System.Xml.XmlDocument doc, string XPath) {
        XmlDocument result = new XmlDocument();

        StringBuilder tmpDoc = new StringBuilder();
        tmpDoc.AppendLine("<?xml version=\"1.0\" encoding=\"Windows-1252\"?>\r\n<content>\r\n");

        foreach(XmlNode n in doc.SelectNodes(XPath))
            tmpDoc.Append(n.OuterXml);

        tmpDoc.AppendLine("</content>");

        result.LoadXml(tmpDoc.ToString());
        return result;
    }

    public static XmlDocument xPathContains(this XmlDocument doc, string XPath, string containsText) {
        return doc.xPath(string.Format("{0}[contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}')]", XPath, containsText));
    }

    public static XmlDocument xPathExcludes(this XmlDocument doc, string XPath, string containsText) {
        return doc.xPath(string.Format("{0}[not(contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}'))]", XPath, containsText));
    }

    public static XmlNodeList DocumentNodes(this XmlDocument doc) {
        return doc.ChildNodes[1].ChildNodes;
    }

    public static string format(this string formatString, params object[] args) {
        return string.Format(formatString, args);
    }

    public static void Print(this string formatString, params object[] args) {
        Console.WriteLine(formatString, args);
    }

    public static void Error(this string formatString, params object[] args) {
        Console.WriteLine(formatString, args);
    }

    public static XmlDocument XmlDoc(this string xmlDoc) {
        if(docCache.ContainsKey(xmlDoc))
            return docCache[xmlDoc];

        var doc = new System.Xml.XmlDocument();
        doc.LoadXml(xmlDoc);
        docCache.Add(xmlDoc, doc);
        return doc;
    }

    public static XmlNodeList XPath(this XmlDocument doc, string XPathExpression, params object[] args) {
        return doc.SelectNodes(XPathExpression.format(args));
    }

    public static XmlNode XPathSingle(this XmlDocument doc, string XPathExpression, params object[] args) {
        return doc.SelectSingleNode(XPathExpression.format(args));
    }

    public static XmlNodeList XPathContains(this XmlDocument doc, string XPathExpression, string containsText) {
        return doc.XPath(string.Format(@"{0}[contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}')]", XPathExpression, containsText));
    }

    public static XmlNodeList XPathExcludes(this XmlDocument doc, string XPathExpression, string containsText) {
        return doc.XPath(string.Format(@"{0}[not(contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}'))]", XPathExpression, containsText));
    }

    public static XmlNodeList XPath(this string doc, string XPathExpression, params object[] args) {
        return doc.XmlDoc().XPath(XPathExpression, args);
    }

    public static XmlNode XPathSingle(this string doc, string XPathExpression, params object[] args) {
        return doc.XmlDoc().XPathSingle(XPathExpression, args);
    }

    public static XmlNodeList XPathContains(this string doc, string XPathExpression, string containsText) {
        return doc.XmlDoc().XPathContains(XPathExpression, containsText);
    }

    public static XmlNodeList XPathExcludes(this string doc, string XPathExpression, string containsText) {
        return doc.XmlDoc().XPathExcludes(XPathExpression, containsText);
    }

    public static XmlNodeList XPath(this XmlNode doc, string XPathExpression, params object[] args) {
        return doc.SelectNodes(XPathExpression.format(args));
    }

    public static XmlNode XPathSingle(this XmlNode doc, string XPathExpression, params object[] args) {
        return doc.SelectSingleNode(XPathExpression.format(args));
    }

    public static XmlNodeList XPathContains(this XmlNode doc, string XPathExpression, string containsText) {
        return doc.XPath(string.Format(@"{0}[contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}')]", XPathExpression, containsText));
    }

    public static XmlNodeList XPathExcludes(this XmlNode doc, string XPathExpression, string containsText) {
        return doc.XPath(string.Format(@"{0}[not(contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}'))]", XPathExpression, containsText));
    }

    public static XmlDocument ToXmlDocument(this XmlNodeList nodeList) {
        StringBuilder tmpDoc = new StringBuilder();
        tmpDoc.AppendLine("<?xml version=\"1.0\" encoding=\"Windows-1252\"?>\r\n<content>\r\n");

        foreach(XmlNode n in nodeList)
            tmpDoc.Append(n.OuterXml);

        tmpDoc.AppendLine("</content>");
        return tmpDoc.ToString().XmlDoc();
    }

    public static XmlNodeList XPath(this XmlNodeList doc, string XPathExpression, params object[] args) {
        return doc.ToXmlDocument().SelectNodes(XPathExpression.format(args));
    }

    public static XmlNode XPathSingle(this XmlNodeList doc, string XPathExpression, params object[] args) {
        return doc.ToXmlDocument().SelectSingleNode(XPathExpression.format(args));
    }

    public static XmlNodeList XPathContains(this XmlNodeList doc, string XPathExpression, string containsText) {
        return doc.ToXmlDocument().XPath(string.Format(@"{0}[contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}')]", XPathExpression, containsText));
    }

    public static XmlNodeList XPathExcludes(this XmlNodeList doc, string XPathExpression, string containsText) {
        return doc.ToXmlDocument().XPath(string.Format(@"{0}[not(contains( translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') , '{1}'))]", XPathExpression, containsText));
    }

    public static string Get(this string url, params object[] args) {
        url = string.Format(url, args);
        string result = "";
        WebResponse objResponse;
        StreamReader sr;
        HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(url);
        try {
            if(credCache != null) {
                objRequest.Credentials = credCache;
                objRequest.PreAuthenticate = true;
            }

            objResponse = objRequest.GetResponse();
            sr = new StreamReader(objResponse.GetResponseStream());
            result = sr.ReadToEnd();
            sr.Close();
            return result;
        }
        catch(Exception) {
        }
        return result;
    }

    public static Dictionary<string, List<string>> Switches(this string[] args) {
        if(switches != null)
            return switches;

        var firstarg = 0;
        switches = new Dictionary<string, List<string>>();

        while(firstarg < args.Length && args[firstarg].StartsWith("--")) {
            string arg = args[firstarg].Substring(2).ToLower();
            string param = "";
            int pos;

            if((pos = arg.IndexOf("=")) > -1) {
                param = args[firstarg].Substring(pos + 3);
                arg = arg.Substring(0, pos);
                if(string.IsNullOrEmpty(param) || string.IsNullOrEmpty(arg)) {
                    "Invalid Option :{0}".Print(args[firstarg].Substring(2).ToLower());
                    switches.Clear();
                    switches.Add("help", new List<string>());
                    return switches;
                }


            }
            if(arg.Equals("load-config")) {
                string file = param;
                // loads the config file, and then continues parsing this line.
                if(File.Exists(file)) {
                    foreach(string line in File.ReadAllLines(param)) {

                        arg = line.Trim();
                        param = "";

                        if(string.IsNullOrEmpty(arg) || arg.StartsWith(";")) // comments
                            continue;

                        // "Config Line {0}".Print(line);

                        if((pos = arg.IndexOf("=")) > -1) {
                            param = arg.Substring(pos + 1);
                            arg = arg.Substring(0, pos);

                            if(string.IsNullOrEmpty(param) || string.IsNullOrEmpty(arg)) {
                                "Invalid Option in config file [{0}]: {1}".Print(file, line.Trim());
                                switches.Add("help", new List<string>());
                                return switches;
                            }

                        }

                        if(!switches.ContainsKey(arg))
                            switches.Add(arg, new List<string>());

                        switches[arg].Add(param);
                    }
                }
                else {
                    "Unable to find configuration file [{0}]".Print(param);
                }
                firstarg++;
                continue;
            }
            if(!switches.ContainsKey(arg))
                switches.Add(arg, new List<string>());

            switches[arg].Add(param);
            firstarg++;
        }
        return switches;
    }

    public static List<string> Parameters(this string[] args) {
        if(parameters != null)
            return parameters;

        var index = 0;
        parameters = new List<string>();

        while(index < args.Length && args[index].StartsWith("--")) {
            index++;
        }

        while(index < args.Length) {
            parameters.Add(args[index]);
            index++;
        }
        return parameters;
    }

    public static void AddBasicAuthCredentials(this WebRequest obj, string url, string username, string password) {
        if(credCache == null)
            credCache = new CredentialCache();

        var uri = new Uri(url);
        credCache.Add(new Uri("{0}://{1}:{2}".format(uri.Scheme, uri.Host, uri.Port)), "Basic", new NetworkCredential(username, password));
    }

    public static Match Match(this string input, string rx_expression) {
        return new Regex(rx_expression).Match(input);
    }

    public static Match MatchIgnoreCase(this string input, string rx_expression) {
        return new Regex(rx_expression, RegexOptions.IgnoreCase).Match(input);
    }

    /// <summary>
    /// Encodes a string into HTML encoding format, encoding control characters as well.
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static string HtmlEncode(this string s) {
        s = System.Web.HttpUtility.HtmlEncode(s);
        var sb = new StringBuilder(s.Length + 100);

        for(var p = 0;p < s.Length;p++)
            sb.Append(s[p] < 31 ? string.Format("&#x{0:x2};", (int)s[p]) : "" + s[p]);

        return sb.ToString();
    }

    public static string HtmlDecode(this string s) {
        return System.Web.HttpUtility.HtmlDecode(s);
    }

    public static string UrlEncode(this string s) {
        return System.Web.HttpUtility.UrlEncode(s);
    }

    public static string UrlDecode(this string s) {
        return System.Web.HttpUtility.UrlDecode(s);
    }

    public static Assembly Assembly(this object obj) {
        return obj.GetType().Assembly;
    }


    public static string Title(this Assembly assembly) {
        try {
            return ((AssemblyTitleAttribute)AssemblyTitleAttribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute))).Title;
        }
        catch { }
        return "";
    }

    public static string Description(this Assembly assembly) {
        try {
            return ((AssemblyDescriptionAttribute)AssemblyDescriptionAttribute.GetCustomAttribute(assembly, typeof(AssemblyDescriptionAttribute))).Description;
        }
        catch { }
        return "";
    }

    public static string Copyright(this Assembly assembly) {
        try {
            return FileVersionInfo.GetVersionInfo(assembly.Location).LegalCopyright;
        }
        catch { }
        return "";
    }

    public static string Company(this Assembly assembly) {
        try {
            return FileVersionInfo.GetVersionInfo(assembly.Location).CompanyName;
        }
        catch { }
        return "";
    }

    public static string Version(this Assembly assembly) {
        try {
            FileVersionInfo vi = FileVersionInfo.GetVersionInfo(assembly.Location);
            return "{0}.{1}.{2}.{3}".format(vi.FileMajorPart, vi.FileMinorPart, vi.FileBuildPart);
        }
        catch { }
        return "";
    }

    public static string Comments(this Assembly assembly) {
        try {
            return FileVersionInfo.GetVersionInfo(assembly.Location).Comments;
        }
        catch { }
        return "";
    }

    public static string Logo(this Assembly assembly) {
        if(logo == null)
            logo = 
@"{0} {1} Version {2} for {3}
{4}. All rights reserved
{5}
-------------------------------------------------------------------------------".format(assembly.Company(), assembly.Title(), assembly.Version(), "x86", assembly.Copyright().Replace("©", "(c)"), assembly.Comments());
        return logo;
    }
    public static void SetLogo(this Assembly assembly, string logoText) {
        logo = logoText;
    }

    public static bool IsSubPathOf(this string childPath, string rootPath) {
        string r = Path.GetFullPath(rootPath);
        string c = Path.GetFullPath(childPath);
        return c.StartsWith(r, StringComparison.CurrentCultureIgnoreCase);
    }

    public static List<string> InnerText(this XmlNodeList nodeList) {
        List<string> result = new List<string>();
        foreach(XmlNode node in nodeList) {
            result.Add(node.InnerText);
        }
        return result;
    }

    public static string GetSubPath(this string parentPath, string childPath) {
        if(childPath.IsSubPathOf(parentPath)) {
            string subDir = Path.GetFullPath(childPath).Substring(parentPath.Length);
            // trim off any leading slashes
            while(subDir.IndexOf("\\") == 0) {
                subDir = subDir.Substring(1);
            }

            // trim off everything up to the last slash.
            while(subDir.Length > 0 && subDir[subDir.Length-1] != '\\') {
                subDir = subDir.Substring(0, subDir.Length - 1);
            }

            // then trim off the last slashes 
            while(subDir.Length > 0 && subDir[subDir.Length-1] == '\\')
                subDir = subDir.Substring(0, subDir.Length - 1);
            return subDir;
        }
        return "";
    }

    public static List<string> SplitToList(this string str, params char[] separator) {
        List<string> result = new List<string>();
        if(!string.IsNullOrEmpty(str))
            result.AddRange(str.Split(separator));

        return result;
    }
    public static string Combine(this List<string> list, char separator) {
        StringBuilder sb = new StringBuilder();
        foreach(string s in list) {
            if(sb.Length > 0)
                sb.Append(separator);
            sb.Append(s);
        }
        return sb.ToString();
    }
    public static string Combine(this List<string> list, string separator) {
        StringBuilder sb = new StringBuilder();
        foreach(string s in list) {
            if(sb.Length > 0)
                sb.Append(separator);
            sb.Append(s);
        }
        return sb.ToString();
    }

    public static List<string> Uniq(this List<string> list) {
        for(int i =0;i<list.Count;i++) {
            for(int j=list.Count-1;j>i;j--) {
                if(list[i].Equals(list[j]))
                    list.RemoveAt(j);
            }
        }
        return list;
    }

    public static int ToInt32(this string str) {
        return str.ToInt32(0);
    }

    public static int ToInt32(this string str, int defaultValue) {
        int i = defaultValue;
        Int32.TryParse(str, out i);
        return i;
    }

    public static void Debug(this string buf, params object[] args) {
        System.Diagnostics.Debug.WriteLine(buf.format(args));
    }

    public static void Debug(this byte[] buf) {
        int lnWidth = 16;
        System.Diagnostics.Debug.WriteLine(" Request Length: {0} [0x{0:x4}]".format(buf.Length));

        for(int x= 0;x< buf.Length;x+=lnWidth) {
            for(int y = 0;y < lnWidth;y++) {
                if(x+y >= buf.Length)
                    System.Diagnostics.Debug.Write("   ");
                else
                    System.Diagnostics.Debug.Write(" {0:x2}".format(buf[x+y]));
            }
            System.Diagnostics.Debug.Write("    ");

            for(int y = 0;y < lnWidth;y++) {
                if(x+y >= buf.Length)
                    System.Diagnostics.Debug.Write(" ");
                else {
                    char c = buf[x + y] < 32 || buf[x + y] > 127 ? '.' : (char)buf[x + y];
                    System.Diagnostics.Debug.Write("{0}".format(c));
                }
            }
            System.Diagnostics.Debug.WriteLine("");
        }
        System.Diagnostics.Debug.WriteLine("");
    }

    public static string CanonicalizePath(this string filename, string filenameHint) {
        string result = filename;
        /*
            {filename}  - substitutes for the original
                          filename, no extension
            {ext}         original extension
            {folder}    - original folder
            {subfolder} - original folder without leading /
            {count}     - a running count of the files
                          downloaded.
            {date}      - the current date (y-m-d)
            {date-long} - the date in long format
            {time}      - the current time (Hh:mm:ss)
            {time-long} - the current time in long fmt
            {ticks}     - the current timestamp as tics
            
         *  
        */
        if(!filenameHint.StartsWith("\\\\")) {
            if(filenameHint.StartsWith("/") || filenameHint.StartsWith("\\"))
                filenameHint=System.Environment.CurrentDirectory.Substring(0, 2)+filenameHint;

            if(filenameHint.IndexOf(":") == -1)
                filenameHint=Path.Combine(System.Environment.CurrentDirectory, filenameHint);
        }

        Uri uri = new Uri(filenameHint);
        filenameHint = uri.AbsolutePath;

        string localPath = uri.LocalPath.Replace("/", "\\");

        result = result.Replace(@"{filename}", Path.GetFileNameWithoutExtension(localPath));
        result = result.Replace(@"{ext}", Path.GetExtension(localPath));
        result = result.Replace(@"{folder}", Path.GetDirectoryName(localPath));
        string pr = Path.GetPathRoot(localPath);
        if(!string.IsNullOrEmpty(pr)) {
            result = result.Replace(@"{subfolder}", Path.GetDirectoryName(localPath).Remove(0, pr.Length));
        }

        result = result.Replace(@"{count}", ""+counter++);
        result = result.Replace(@"{date}", DateTime.Now.ToString("yyyy-MM-dd"));
        result = result.Replace(@"{date-long}", DateTime.Now.ToString("MMMM dd YYYY"));
        result = result.Replace(@"{time}", DateTime.Now.ToString("hh-mm-ss"));
        result = result.Replace(@"{time-long}", DateTime.Now.ToString("HH-mm-ss-ffff"));
        result = result.Replace(@"{ticks}", ""+DateTime.Now.Ticks);

        return result;
    }

    public static Uri CanonicalizeUri(this string uri) {
        Uri finalUri = new Uri(uri, UriKind.RelativeOrAbsolute);
        if(!finalUri.IsAbsoluteUri)
            finalUri = new Uri(new Uri("http://localhost"), finalUri);

        return finalUri; 
    }
}
class ConsoleColors :IDisposable {
    ConsoleColor _fg;
    ConsoleColor _bg;
    public ConsoleColors(ConsoleColor fg, ConsoleColor bg) {
        _fg = Console.ForegroundColor;
        _bg = Console.BackgroundColor;
        Console.ForegroundColor = fg;
        Console.BackgroundColor = bg;
    }

    public void Dispose() {
        Console.ForegroundColor = _fg;
        Console.BackgroundColor = _bg;
    }
}